package org.zetaframework.controller.extra;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileMagicNumber;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.handler.WriteHandler;
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.zetaframework.annotation.log.SysLog;
import org.zetaframework.annotation.satoken.PreCheckPermission;
import org.zetaframework.annotation.satoken.PreMode;
import org.zetaframework.base.excel.convert.BigNumberConverter;
import org.zetaframework.base.excel.handler.ExcelStyleHandler;
import org.zetaframework.base.excel.handler.FreezePaneHandler;
import org.zetaframework.base.param.ExportExcelParam;
import org.zetaframework.controller.BaseController;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Excel导出Controller
 *
 * @param <ExportBean>  Excel导出实体
 * @param <Entity>      实体
 * @param <QueryParam>  查询参数
 *
 * @author gcc
 */
public interface ExportController<ExportBean, Entity, QueryParam> extends BaseController<Entity> {

    /**
     * 获取导出实体的类型
     */
    default Class<ExportBean> getExportExcelClass() {
        Optional<Type> first = Arrays.stream(this.getClass().getGenericInterfaces())
                // 筛选出类上面的ExportController接口
                .filter(it -> Objects.equals(((ParameterizedType) it).getRawType().getTypeName(), ExportController.class.getTypeName()))
                .findFirst();
        if (first.isEmpty()) { return null; }

        Type type = first.get();
        // 获取当前接口的第一个泛型的值(下标从0开始)。 即ExportBean的值
        Type argument = ((ParameterizedType) type).getActualTypeArguments()[0];
        return (Class<ExportBean>) argument;
    }

    /**
     * 导出Excel
     *
     * @param param  导出Excel数据 参数
     * @param response 响应
     */
    @PreCheckPermission(value = {"{}:export", "{}:view"}, mode = PreMode.OR)
    @Operation(summary = "导出Excel", description = """
    <pre>
    导出参数示例：
    {
        fileName: "用户列表",   // 【必传】excel文件名,不带扩展名.xlsx
        queryParam: {},        // 【非必传】查询条件
        sheetName: null,       // 【非必传】sheet名称,不传默认为：Sheet1
        freezeHeader: null,    // 【非必传】是否冻结表头，不传默认为：true
        timestampSuffix: null, // 【非必传】文件名是否添加时间戳后缀，不传默认为：true
    }
    </pre>
    """)
    @SysLog(response = false)
    @PostMapping(value = "/export", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    default void getImportTemplate(@RequestBody @Validated ExportExcelParam<QueryParam> param, HttpServletResponse response) {
        try {
            String fileName = param.getFileName();
            // 判断文件名是否添加时间戳
            if (Boolean.TRUE.equals(param.getTimestampSuffix())) {
                fileName = StrUtil.format("{}_{}", fileName, System.currentTimeMillis());
            }

            // 获取导入实体类
            Class<ExportBean> exportClass = getExportExcelClass();
            if (exportClass == null) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "无法获取导入模板");
                return;
            }

            // 设置响应头
            response.setContentType(FileMagicNumber.XLSX.getMimeType());
            response.setCharacterEncoding("utf-8");
            // 将文件名中的特殊字符转换为URL安全的格式
            String filename = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
            String contentDisposition = StrUtil.format("attachment;filename*=utf-8''{}.xlsx", filename);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);

            // 创建ExcelWriterBuilder
            ExcelWriterSheetBuilder sheet = FastExcel.write(response.getOutputStream(), exportClass).sheet();
            sheet.sheetName(param.getSheetName());

            // 注册写处理器 https://fast-excel.github.io/fastexcel/zh-cn/docs/write/extra#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8
            List<WriteHandler> writeHandlers = new ArrayList<>();
            // 如果冻结表头，则注册冻结表头处理器
            if (Boolean.TRUE.equals(param.getFreezeHeader())) { writeHandlers.add(new FreezePaneHandler()); }
            // 注册-样式处理器
            writeHandlers.add(new HorizontalCellStyleStrategy(
                    ExcelStyleHandler.getHeadStyle(), ExcelStyleHandler.getContentStyle()));
            // 注册-自定义写处理器
            registerExportWriteHandlers(writeHandlers);
            writeHandlers.forEach(sheet::registerWriteHandler);

            // 注册-大数字转换器
            sheet.registerConverter(new BigNumberConverter());
            // 写入数据
            sheet.doWrite(findExportList(param.getQueryParam()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>注册导出Excel 自定义写处理器</p>
     *
     * 说明：<br>
     * 如果你要导出的excel中有一些额外信息，请重写本方法
     * <p>
     * 参考：<br>
     * <a href="https://fast-excel.github.io/fastexcel/zh-cn/docs/write/extra#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8">自定义拦截器</a>
     *
     * @param handlers 写处理器列表
     */
    default void registerExportWriteHandlers(List<WriteHandler> handlers) { }

    /**
     * 获取待导出的数据
     *
     * @param param 查询参数 不可为空
     * @return List<ExportBean>
     */
    default List<ExportBean> findExportList(QueryParam param) {
        // 构造查询条件
        Entity entity = BeanUtil.toBean(param, getEntityClass());

        // 条件查询Entity数据
        List<Entity> list = getBaseService().list(new QueryWrapper<>(entity));
        if (CollUtil.isEmpty(list)) { return Collections.emptyList(); }

        // Entity -> ExportBean
        return list.stream()
                .map(it -> BeanUtil.toBean(it, getExportExcelClass()))
                .collect(Collectors.toList());
    }

}
